using System;
using System.Collections.Generic;
using System.Reflection;
using Unity.Extensions;
using Xunit;

namespace Unity.Extensions
{
    public class GenericClassesVirtualInterceptorTest
    {
        [Fact]
        public void GenericMethod()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(GenericSpy<>).GetMethod("GenericMethod");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            GenericSpy<int> result = WrapAndCreateType<GenericSpy<int>>(dictionary);
            result.GenericMethod(46, "2");

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data 46 and 2", Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
        }

        [Fact]
        public void NonGenericMethod()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(GenericSpy<>).GetMethod("MethodWhichTakesGenericData");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            GenericSpy<int> result = WrapAndCreateType<GenericSpy<int>>(dictionary);
            result.MethodWhichTakesGenericData(24);

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data 24", Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
        }

        [Fact]
        public void ReturnsDataOfGenericType()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(GenericSpy<>).GetMethod("GenericReturn");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            GenericSpy<int> result = WrapAndCreateType<GenericSpy<int>>(dictionary);
            int value = result.GenericReturn(256.9);

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data 256.9", Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
            Assert.Equal(default(int), value);
        }

        [Fact]
        public void WhereClauseOnClass()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(GenericSpyWithWhereClause<>).GetMethod("Method");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            GenericSpyWithWhereClause<Foo> result = WrapAndCreateType<GenericSpyWithWhereClause<Foo>>(dictionary);
            result.Method(new Foo());

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data " + typeof(Foo).FullName, Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
        }

        static T WrapAndCreateType<T>(IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers,
                                      params object[] ctorArgs)
        {
            Type wrappedType = VirtualInterceptor.WrapClass(typeof(T));
            ILEmitProxy proxy = new ILEmitProxy(handlers);
            List<object> wrappedCtorArgs = new List<object> { proxy };
            wrappedCtorArgs.AddRange(ctorArgs);
            return (T)Activator.CreateInstance(wrappedType, wrappedCtorArgs.ToArray());
        }

        public class Foo : IFoo {}

        public class GenericSpy<T>
        {
            public virtual void GenericMethod<T1>(T data,
                                                  T1 data1)
            {
                Recorder.Records.Add("In method with data " + data + " and " + data1);
            }

            public virtual T GenericReturn<T1>(T1 data)
            {
                Recorder.Records.Add("In method with data " + data);
                return default(T);
            }

            public virtual void MethodWhichTakesGenericData(T data)
            {
                Recorder.Records.Add("In method with data " + data);
            }
        }

        public class GenericSpyWithWhereClause<T>
            where T : class, IFoo
        {
            public virtual void Method(T data)
            {
                Recorder.Records.Add("In method with data " + data);
            }
        }

        public interface IFoo {}
    }
}